/*
 *    Moment.java
 *    Copyright (C) 2011 University of Waikato, Hamilton, New Zealand
 *    @author Maciek Jarka (www.admire-project.eu)
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package moa.associations;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

import sizeof.agent.SizeOfAgent;

import moa.core.ObjectRepository;
import moa.options.FloatOption;
import moa.options.IntOption;
import moa.options.OptionHandler;
import moa.options.Options;
import moa.tasks.TaskMonitor;
import weka.associations.Item;
import weka.associations.NominalItem;
import weka.core.Instance;

/** Main implementation of the algorithm Moment based on:
 *
 * Yun Chi, Haixun Wang, Philip S. Yu, Richard R. Muntz: Moment: Maintaining Closed Frequent Itemsets over a Stream Sliding Window. ICDM 2004: 59-66
 * 
 * @author maciek
 *
 */
public class Moment implements Associator {

    Set<Item> singletons;

    MomentNode root;

    Queue<Transaction> window;

    int noTransactions;

    FPNode fpRoot;

    Options options;

    // Algorithm options
    // Index of attribute value indicating, that object is not present in the transaction.
    // For example, when nominal values for the attribute are true, false , the value of this option
    // should be 1 (index of "false")
    private IntOption attrMissingIndexOption = new IntOption("presentAttrIndex", 'I', "Present attribute index", 0);
    // Minimum support as fraction

    private FloatOption minSupport = new FloatOption("minimumSupport", 'S', "Minimal support", 0);
    // Fixed size of time window

    private IntOption windowSize = new IntOption("windowSize", 'w', "Window size", 10);

    /** Gets ArrayList of items from WEKA instance (transaction)
     *
     * @param transaction - instance
     * @return
     */
    private ArrayList<Item> getSingletons(Instance transaction) {

        ArrayList<Item> items = new ArrayList<Item>();
        for (int i = 0; i < transaction.numAttributes(); i++) {
            if ((transaction.value(i) == Double.valueOf(this.attrMissingIndexOption.getValue()))) {
                try {
                    NominalItem item = new NominalItem(transaction.attribute(i), attrMissingIndexOption.getValue());
                    items.add(item);

                } catch (Exception exc) {
                    exc.printStackTrace();
                }
            }
        }
        return items;
    }

    /** Initiatialize parameters of the algorithm
     *
     */
    public Moment() {
        singletons = new HashSet<Item>();
        fpRoot = new FPNode(null, null);
        root = new MomentNode(MomentNodeType.ROOT);
        noTransactions = 0;
        window = new ConcurrentLinkedQueue<Transaction>();
        options = new Options();
        options.addOption(attrMissingIndexOption);
        options.addOption(minSupport);
        options.addOption(windowSize);
        //MomentNode.window = window;
        MomentNode.minSupport = minSupport.getValue();
    }

    public String toString() {
        String retstr = "Frequent itemsets: \n";
        List<MomentNode> nodes = root.getFrequentNodes();

        for (MomentNode node : nodes) {
            retstr += node.toString() + ": " + node.support + " Type:" + node.type + "\n";
        }
        retstr += "\n\n";
        return retstr;
    }

    /** Main method of the algorithm. Rebuilds the model with new example (transaction).
     *
     */
    @Override
    public void addTransaction(Instance transaction) {

        // get items from Weka instance
        List<Item> t = getSingletons(transaction);

        // if transaction has items
        if (t.size() > 0) {
            System.out.println("New transaction: " + t);
            // add new itemset to frequent pattern tree
            fpRoot.add(++noTransactions, t);
            //System.out.println("Support: " + fpRoot.headers.calculateSupport(t));
            // remove old transaction


            // First launch of explore method on Moment tree root starts after first time window is filled
            if (noTransactions == windowSize.getValue()) {
                for (FPNode n : FPNode.headers.headers) {
                    List<Item> items = new ArrayList<Item>();
                    items.add(n.item);
                    MomentNode mnode = new MomentNode(MomentNodeType.INFREQUENT_GATEWAY, items, root);
                    root.children.add(mnode);
                    Collections.sort(root.children, new MomentNode.NodeComparator());
                }
                for (MomentNode n : root.children) {
                    MomentNode.explore(n);
                }

            }

            // When time window is full and new transaction appears, two operations are performed:
            // - addition on new transaction
            // - deletion on tail of time window
            if (noTransactions > windowSize.getValue()) {

                root.addition(new Transaction(noTransactions, getSingletons(transaction)));
                root.deletion(fpRoot.headers.remove(noTransactions - windowSize.getValue()));
            }
        }


    }

    @Override
    public boolean canProduceRules() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public OptionHandler copy() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getCLICreationString(Class<?> expectedType) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Options getOptions() {
        return options;
    }

    @Override
    public String getPurposeString() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void prepareForUse() {
        // TODO Auto-generated method stub
    }

    @Override
    public void prepareForUse(TaskMonitor monitor, ObjectRepository repository) {
        // TODO Auto-generated method stub
    }

    @Override
    public void getDescription(StringBuilder sb, int indent) {
        // TODO Auto-generated method stub
    }

    @Override
    public int measureByteSize() {
        // TODO Auto-generated method stub

        return (int) SizeOfAgent.fullSizeOf(root) + (int) SizeOfAgent.fullSizeOf(fpRoot);
    }
}
